﻿using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Threading;
using System.Threading.Tasks;

namespace Dapper.Sharding
{
#if NET462
    public class DistributedTransaction : IDisposable
#else
    public class DistributedTransaction : IDisposable, IAsyncDisposable
#endif
    {
        private IDatabase defaultDb = null;
        private (DbConnection, DbTransaction) defaultVal = default;
        private Dictionary<IDatabase, (DbConnection, DbTransaction)> dict = null;

        private static (DbConnection, DbTransaction) CreateConnAndTran(IDatabase db)
        {
            DbConnection conn = null;
            DbTransaction tran = null;
            try
            {
                conn = db.GetConn();
                tran = conn.BeginTransaction();
                return (conn, tran);
            }
            catch
            {
                try
                {
                    tran?.Dispose();
                }
                catch { }

                try
                {
                    conn?.Dispose();
                }
                catch { }

                throw;
            }
        }

        private static async Task<(DbConnection, DbTransaction)> CreateConnAndTranAsync(IDatabase db)
        {
            DbConnection conn = null;
            DbTransaction tran = null;
            try
            {
                conn = await db.GetConnAsync();
#if NET462
                tran = conn.BeginTransaction();
#else
                tran = await conn.BeginTransactionAsync();
#endif
                return (conn, tran);
            }
            catch
            {
                if (tran != null)
                {
                    try
                    {
#if NET462
                        tran.Dispose();
#else
                        await tran.DisposeAsync();
#endif
                    }
                    catch { }
                }
                if (conn != null)
                {
                    try
                    {
#if NET462
                        conn.Dispose();
#else
                        await conn.DisposeAsync();
#endif
                    }
                    catch
                    {

                    }
                }
                throw;
            }

        }

        public (DbConnection, DbTransaction) GetVal(IDatabase db)
        {
            if (defaultDb == null) //第一次初始化
            {
                defaultVal = CreateConnAndTran(db);
                defaultDb = db;
                return defaultVal;
            }
            else if (defaultDb.Equals(db))
            {
                return defaultVal;
            }
            else
            {
                if (dict == null)//dict为null第一次创建
                {
                    dict = [];
                    var val = CreateConnAndTran(db);
                    dict.Add(db, val);
                    return val;
                }
                else
                {
                    var ok = dict.TryGetValue(db, out var val);
                    if (!ok)
                    {
                        val = CreateConnAndTran(db);
                        dict.Add(db, val);
                    }
                    return val;
                }
            }
        }

        public async ValueTask<(DbConnection, DbTransaction)> GetValAsync(IDatabase db)
        {
            if (defaultDb == null) //第一次初始化
            {
                defaultVal = await CreateConnAndTranAsync(db);
                defaultDb = db;
                return defaultVal;
            }
            else if (defaultDb.Equals(db))
            {
                return defaultVal;
            }
            else
            {
                if (dict == null)//dict为null第一次创建
                {
                    dict = [];
                    var val = await CreateConnAndTranAsync(db);
                    dict.Add(db, val);
                    return val;
                }
                else
                {
                    var ok = dict.TryGetValue(db, out var val);
                    if (!ok)
                    {
                        val = await CreateConnAndTranAsync(db);
                        dict.Add(db, val);
                    }
                    return val;
                }
            }
        }

        private readonly Dictionary<IDatabase, bool> TranStatus = [];

        public void Commit()
        {
            if (done)
            {
                return;
            }

            if (defaultDb != null)
            {
                //提交事务
                defaultVal.Item2.Commit();

                TranStatus.Add(defaultDb, true);

                //释放事务
                try
                {
                    defaultVal.Item2.Dispose();
                }
                catch { }

                //释放连接
                try
                {
                    defaultVal.Item1.Dispose();
                }
                catch { }

                defaultDb = null;
                defaultVal.Item2 = null;
                defaultVal.Item1 = null;
            }

            if (dict != null && dict.Count > 0)
            {
                foreach (var d in dict)
                {
                    var item = d.Value;

                    //提交事务
                    item.Item2.Commit();

                    TranStatus.Add(d.Key, true);

                    //释放事务
                    try
                    {
                        item.Item2.Dispose();
                    }
                    catch { }

                    //释放连接
                    try
                    {
                        item.Item1.Dispose();
                    }
                    catch { }
                }
                dict.Clear();
                dict = null;
            }

            done = true;
        }

        public Dictionary<IDatabase, bool> Rollback()
        {
            if (done)
            {
                return TranStatus;
            }

            if (defaultDb != null)
            {
                if (!TranStatus.ContainsKey(defaultDb))
                {
                    TranStatus.Add(defaultDb, false);

                    //回滚事务
                    try
                    {
                        defaultVal.Item2.Rollback();
                    }
                    catch { }

                    //释放事务
                    try
                    {
                        defaultVal.Item2.Dispose();
                    }
                    catch { }

                    //释放连接
                    try
                    {
                        defaultVal.Item1.Dispose();
                    }
                    catch { }

                    defaultDb = null;
                    defaultVal.Item2 = null;
                    defaultVal.Item1 = null;
                }
            }

            if (dict != null && dict.Count > 0)
            {
                foreach (var d in dict)
                {
                    var item = d.Value;
                    if (!TranStatus.ContainsKey(d.Key))
                    {
                        TranStatus.Add(d.Key, false);

                        //回滚事务
                        try
                        {
                            item.Item2.Rollback();
                        }
                        catch { }

                        //释放事务
                        try
                        {
                            item.Item2.Dispose();
                        }
                        catch { }

                        //释放连接
                        try
                        {
                            item.Item1.Dispose();
                        }
                        catch { }
                    }
                }
                dict.Clear();
                dict = null;
            }

            done = true;
            return TranStatus;
        }


#if !NET462

        public async Task CommitAsync(CancellationToken cToken = default)
        {
            if (done)
            {
                return;
            }

            if (defaultDb != null)
            {
                //提交事务
                await defaultVal.Item2.CommitAsync(cToken);

                TranStatus.Add(defaultDb, true);

                //释放事务
                try
                {
                    await defaultVal.Item2.DisposeAsync();
                }
                catch { }

                //释放连接
                try
                {
                    await defaultVal.Item1.DisposeAsync();
                }
                catch { }

                defaultDb = null;
                defaultVal.Item2 = null;
                defaultVal.Item1 = null;
            }

            if (dict != null && dict.Count > 0)
            {
                foreach (var d in dict)
                {
                    var item = d.Value;

                    //提交事务
                    await item.Item2.CommitAsync(cToken);
                    
                    TranStatus.Add(d.Key, true);

                    //释放事务
                    try
                    {
                        await item.Item2.DisposeAsync();
                    }
                    catch { }

                    //释放连接
                    try
                    {
                        await item.Item1.DisposeAsync();
                    }
                    catch { }
                }
                dict.Clear();
                dict = null;
            }

            done = true;
        }

        public async Task<Dictionary<IDatabase, bool>> RollbackAsync(CancellationToken cToken = default)
        {
            if (done)
            {
                return TranStatus;
            }

            if (defaultDb != null)
            {
                if (!TranStatus.ContainsKey(defaultDb))
                {
                    TranStatus.Add(defaultDb, false);

                    //回滚事务
                    try
                    {
                        await defaultVal.Item2.RollbackAsync(cToken);
                    }
                    catch { }

                    //释放事务
                    try
                    {
                        await defaultVal.Item2.DisposeAsync();
                    }
                    catch { }

                    //释放连接
                    try
                    {
                        await defaultVal.Item1.DisposeAsync();
                    }
                    catch { }

                    defaultDb = null;
                    defaultVal.Item2 = null;
                    defaultVal.Item1 = null;
                }
            }

            if (dict != null && dict.Count > 0)
            {
                foreach (var d in dict)
                {
                    var item = d.Value;
                    if (!TranStatus.ContainsKey(d.Key))
                    {
                        TranStatus.Add(d.Key, false);

                        //回滚事务
                        try
                        {
                            await item.Item2.RollbackAsync(cToken);
                        }
                        catch { }

                        //释放事务
                        try
                        {
                            await item.Item2.DisposeAsync();
                        }
                        catch { }

                        //释放连接
                        try
                        {
                            await item.Item1.DisposeAsync();
                        }
                        catch { }
                    }
                }
                dict.Clear();
                dict = null;
            }

            done = true;
            return TranStatus;
        }

#endif

        private bool done = false;

        #region Dispose

        private bool disposedValue;

        protected virtual void Dispose(bool disposing)
        {
            if (!disposedValue)
            {
                if (disposing)
                {
                    if (!done)
                    {
                        Rollback();
                    }
                }
                disposedValue = true;
            }
        }


        public void Dispose()
        {
            Dispose(disposing: true);
            GC.SuppressFinalize(this);
        }

#if !NET462

        public async ValueTask DisposeAsync()
        {
            if (!done)
            {
                await RollbackAsync();
                disposedValue = true;
            }
            GC.SuppressFinalize(this);
        }

#endif
        #endregion
    }
}
